﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace InterlockStudy
{
    internal class Node<T>
    {
        public T Data { get; set; }

        public bool HasValue { get; set; }
    }

    class RingBuffer<T>
    {
        private readonly Node<T>[] _nodes;
        private long _tailIndex = -1;
        private long _headIndex = -1;
        private AutoResetEvent _readEvent = new AutoResetEvent(false);
        private AutoResetEvent _writeEvent = new AutoResetEvent(false);

        public RingBuffer(int maxSize)
        {
            _nodes = new Node<T>[maxSize];

            for (var i = 0; i < maxSize; i++)
            {
                _nodes[i] = new Node<T>();
            }
        }

        public void EnQueue(T data)
        {
            while (true)
            {
                if (this.TryEnQueue(data))
                {
                    _readEvent.Set();
                    return;
                }

                _writeEvent.WaitOne();
            }

        }

        public T DeQueue()
        {
            while (true)
            {
                T data;
                if (this.TryDeQueue(out data))
                {
                    _writeEvent.Set();
                    return data;
                }

                _readEvent.WaitOne();
            }

        }

        public bool TryEnQueue(T data)
        {
            long localTailIndex, newTailIndex, currentTailIndex;
            do
            {
                localTailIndex = _tailIndex;

                if (!this.CanWrite(localTailIndex))
                {
                    return false;
                }

                newTailIndex = localTailIndex + 1;

                if (_nodes[newTailIndex % _nodes.Length].HasValue)
                {
                    return false;
                }

                currentTailIndex = Interlocked.CompareExchange(ref _tailIndex, newTailIndex, localTailIndex);
            }
            while (localTailIndex != currentTailIndex);

            _nodes[newTailIndex % _nodes.Length].Data = data;
            _nodes[newTailIndex % _nodes.Length].HasValue = true;

            return true;
        }

        public bool TryDeQueue(out T data)
        {
            long localHeadIndex, newHeadIndex, currentHeadIndex;
            do
            {
                localHeadIndex = _headIndex;

                if (!this.CanRead(localHeadIndex))
                {
                    data = default(T);
                    return false;
                }

                newHeadIndex = localHeadIndex + 1;
                if (_nodes[newHeadIndex % _nodes.Length].HasValue == false)
                {
                    data = default(T);
                    return false;
                }

                currentHeadIndex = Interlocked.CompareExchange(ref _headIndex, newHeadIndex, localHeadIndex);
            }
            while (localHeadIndex != currentHeadIndex);

            data = _nodes[newHeadIndex % _nodes.Length].Data;
            _nodes[newHeadIndex % _nodes.Length].HasValue = false;

            return true;
        }

        private bool CanWrite(long localTailIndex)
        {
            return localTailIndex - _headIndex < _nodes.Length;
        }

        private bool CanRead(long localHeadIndex)
        {
            return _tailIndex - localHeadIndex > 0;
        }
    }
}
